#include "agent01.h"

agent01::agent01(int numberOfKeywords_, double monthBudget_){
    srand(time(NULL));
    numberOfKeywords=numberOfKeywords_;
    monthBudget=monthBudget_;
    budgetRemaining=monthBudget;
    clicksPerKeyword = new int[numberOfKeywords_];
    impressionsPerKeyword = new int[numberOfKeywords_];
    costPerKeyword = new double[numberOfKeywords_];
    positionPerKeyword = new double[numberOfKeywords_];
    bidsSet = new bool[numberOfKeywords_];
    tempBids = new struct tTempBids[numberOfKeywords_];
    nextBids = new double[numberOfKeywords_];
    nextBudget = new double[numberOfKeywords_];
    for(int i =0;i < 7;i++)
    {
        totalClicksPerKeyword[i] = new int[numberOfKeywords_];
    }
}

agent01::~agent01(){
    //////////////////////////////////////////////////
    ///  Ensure that all created variables are properly de-allocated here
    //////////////////////////////////////////////////
    delete clicksPerKeyword;
    delete impressionsPerKeyword;
    delete costPerKeyword;
    delete positionPerKeyword;
    delete bidsSet;
    delete tempBids;
    delete nextBids;
    delete nextBudget;
    for(int i =0;i < 7;i++)
    {
        delete totalClicksPerKeyword[i];
    }


    //////////////////////////////////////////////////
    //////////////////////////////////////////////////
}

/**
 * @brief agent01::initializeMonth
 * run on the beginning of each month, initializes the budget
 */
void agent01::initializeMonth(int month)
{
    if(month = 0)
        budgetRemaining = monthBudget;
    else
        budgetRemaining += 1000;
    currentMonth = month;
    weekNumber = 0;
    currentMonthDay = 1;
}

/**
* @brief agent01::receiveInfo called in front of each day
* @param month current month (0..3)
* @param day current day of week (0..6)
* @param imps_ previous day impressions per keyword
* @param clicks_ previous day clicks per keyword
* @param cost_ previous day cost per keyword
* @param pos_ previous day position per keyword (0-based)
*/
void agent01::receiveInfo(int month, int day, int *imps_, int* clicks_, double* cost_, double* pos_){

    double totalCost=0.0;
    for(int i=0;i<numberOfKeywords;i++){
        impressionsPerKeyword[i]=imps_[i];
        clicksPerKeyword[i]=clicks_[i];
        costPerKeyword[i]=cost_[i];
        positionPerKeyword[i]=pos_[i];
        totalCost+=cost_[i];
        //resetting some own values
        bidsSet[i] = false;
    }

    //////////////////////////////////////////////////
    ///  Below here, make sure you store the info elsewhere and/or make your decisions here.
    ///  Please ensure that when the month switches you get 1000 euro's extra budget
    //////////////////////////////////////////////////
    budgetRemaining -= totalCost;

    if(day < currentWeekDay)
        weekNumber++;
    if(currentWeekDay != day)
        currentMonthDay++;
    currentWeekDay = day;

    //if month switched
    if(currentMonth != month)
        initializeMonth(month);

    if(currentMonthDay <= 7)
    {//first week learning strategy, we just bid lowest, sit quietly and learn :P
        firstWeekStrategy();
        totalClickAmountLearn();
        return;
    }

    totalClickAmountLearn();
    //: Every day adjust available budget based on what is expected to be spent until end of the month
    //: Every day adjust the available budget based on cyclic daily variations in clicks
    initialAdjustBudget();
    //: Strategy 4c - decreasing too big bids
    strategy4c();
    //: Strategy 4b - decreasing bid if the lower one is unproportionally low - opportunity to save some money
    strategy4b();
    //: Strategy 4a - increasing a bid randomly up to the maximum bid
    strategy4a();

    //: Strategy 5 - additional budget for low bids
    finalAdjustBudget();
    //////////////////////////////////////////////////
    //////////////////////////////////////////////////
}

/**
 * @brief tempBidsCompare
 * function for quicksorting bids based on the amount
 * @param tbid1
 * @param tbid2
 * @return
 */
int tempBidsCompare(const void *vbid1,const void *vbid2)
{
    struct tTempBids *bid1 = (struct tTempBids *)vbid1;
    struct tTempBids *bid2 = (struct tTempBids *)vbid2;
    if(bid1->bid < bid2->bid)
        return -1;
    if(bid1->bid > bid2->bid)
        return 1;
    return 0;
}

/**
 * @brief agent01::finalAdjustBudget
 * Additional budget for low bids (depending how much we have left)
 * Because we want to get maximum clicks for lowest prices
 * and we cannot be sure how many we will actually get
 */
void agent01::finalAdjustBudget()
{
    double budgetLeft = (budgetRemaining - budgetForDay)/2;
    if(budgetLeft > 0)
    {
        for(int i =0; i < numberOfKeywords ;i++)
        {
            tempBids[i].bid = nextBids[i];
            tempBids[i].index = i;
        }
        //we sort the bids from lowest to highest
        qsort(tempBids,numberOfKeywords,sizeof(struct tTempBids),tempBidsCompare);

        /*
         * we give to the first half of sorted bids
         * the remaining part of the budget
         * giving each next bid half of the budget given to previous bid
         */
        int share = 2;
        for(int i =0; i < numberOfKeywords/2 ;i++)
        {
            int index = tempBids[i].index;
            nextBudget[index] += budgetLeft/share;
            share = share >> 1;
        }

    }
}

/**
 * @brief agent01::getClickAmount
 * Gets the predicted amount of clicks for specific position
 * @param position
 * @param total
 * @return
 */
double agent01::getClickAmount(double position,int total)
{
    return PClick*pow(1-PClick,position)*total;
}

/**
 * @brief agent01::initialAdjustBudget
 * Here we calculate how much budget more do we need till the end of the month
 * and set preliminary budget for this day
 */
void agent01::initialAdjustBudget()
{
    double CPC;
    double costSum = 0;
    //we count the sum of costs starting from the following day
    //until the end of the month
    //assuming current position and cost
    for(int day=0;day<7;day++)
        for(int i =0; i < numberOfKeywords;i++)
        {
            CPC = costPerKeyword[i]/clicksPerKeyword[i];

            int r = 0;
            if(currentWeekDay+1 <= day)
            {
                int d = currentMonthDay + day - (currentWeekDay+1);
                r = ((30-d)/7)+1; // how many such days are left till the end of the month
            } else
            {
                int d = currentMonthDay + day - (currentWeekDay+1-7);
                r = ((30-d)/7)+1; // how many such days are left till the end of the month
            }
            if(r >= 0)
            {
                costSum += r*(CPC*getClickAmount(positionPerKeyword[i],totalClicksPerKeyword[day][i]));
            }
        }

    budgetForDay = budgetRemaining - costSum;

    int clicksForDay = 0; // the amount of clicks that can happen this day
    for(int i =0; i < numberOfKeywords;i++)
        clicksForDay += totalClicksPerKeyword[currentWeekDay][i];


    //now we set up the budget
    for(int i =0; i < numberOfKeywords;i++)
    {
        if(budgetForDay <= 0)
        {
            nextBudget[i]=0;
            bidsSet[i] = true;
            nextBids[i] = 1.0/100000000.0;
        }
        else
        {
            //we split nicely the budget
            nextBudget[i] = ((double)(totalClicksPerKeyword[currentWeekDay][i]/clicksForDay))*budgetForDay;
        }
    }
}

/**
 * @brief agent01::strategy4a
 * Third and last basic strategy - random bid increase
 * the range is from current cost of click (meaning bid of lower positioned bidder)
 * up to maximum for our budget
 * We do this in hope that we will find some cheap bid where we can gain more clicks
 */
void agent01::strategy4a()
{

    double maxBid;
    for(int i =0; i < numberOfKeywords;i++)
    {
        if(!bidsSet[i] && positionPerKeyword[i] > 0)
        {
            bidsSet[i] = true;
            maxBid = nextBudget[i]/getClickAmount(positionPerKeyword[i]-1,totalClicksPerKeyword[currentWeekDay][i]);
            nextBids[i]= ((maxBid-costPerKeyword[i])*getRandBetween0and1())+costPerKeyword[i];
        }
    }

}

/**
 * @brief agent01::strategy4b
 * Second basic strategy - decrease a bid if the lower one is unproportionally low - to save some money per click
 * only if we are not in the last week
 */
void agent01::strategy4b()
{
    for(int i =0; i < numberOfKeywords;i++)
    {
        if(!bidsSet[i] && costPerKeyword[i] < (nextBids[i]*(1-2*PClick)))
        {
            //it would be a very big saving if we would decrease our position now
            //but we will do that only if not in the last 7 days of month
            if(currentMonthDay < 23)
            {
                bidsSet[i] = true;
                nextBids[i] = 0.99*costPerKeyword[i];
            }

        }
    }

}

/**
 * @brief agent01::strategy4a
 * First basic strategy - if we exceeded our budget for that keyword - decrease the bid for it
 */
void agent01::strategy4c()
{
    if(!(currentMonth==0 && weekNumber==0))
    for(int i =0; i < numberOfKeywords;i++)
    {
        if(!bidsSet[i] && costPerKeyword[i]>= nextBudget[i]-nextBids[i])
        {// we reached the limit of our budget for this one
            //set the next bid for it at 99% of our current cost per click
            //to deliberately decrease our position
            nextBids[i] = 0.99*(costPerKeyword[i]/clicksPerKeyword[i]);
            bidsSet[i] = true;
        }
    }
}

/**
 * @brief agent01::totalClickAmountLearn
 * This is to gain estimation of total amount of clicks that one can
 * get from specific keywords on specific days
 */
void agent01::totalClickAmountLearn()
{
    for(int i =0;i < numberOfKeywords ; i++)
    {
        int maxClicks = (int)(((double)clicksPerKeyword[i]) /
                              (PClick*pow(1-PClick,positionPerKeyword[i])));
        if(currentMonth == 0 && weekNumber == 0)
        {// on first week we just learn
            totalClicksPerKeyword[currentWeekDay][i] = maxClicks;
        }
        else
        {// on rest of the weeks we average out
            totalClicksPerKeyword[currentWeekDay][i] = (totalClicksPerKeyword[currentWeekDay][i]+maxClicks)/2;
        }
    }
}

/**
 * @brief agent01::power gives a to the power of b
 * @param a
 * @param b
 * @return
 */
double agent01::power(double a, int b)
{
    if(b == 0)
        return 1;
    if(b < 0)
    {
        a = 1/a;
        b = -b;
    }
    if(b == 1)
        return a;
    double result = a;
    while(b-->1)
    {
        result = result * a;
    }
    return result;
}

/**
 * @brief agent01::firstWeekStrategy
 * This strategy is to bid lowest and learn few things
 */
void agent01::firstWeekStrategy()
{
    for(int i=0;i<numberOfKeywords;i++)
    {
        nextBids[i]=1.0/100000000.0;
        nextBudget[i]=budgetRemaining/numberOfKeywords;
    }
}

/**
 * @brief agent01::getBidsForAllKeywords
 * @return return what we want to bid for each keyword this day
 */
double* agent01::getBidsForAllKeywords(){
    double* allBids = new double[numberOfKeywords];     //Don't worry about garbage collection, is handled elsewhere!!!
    for(int i=0;i<numberOfKeywords;i++)
        allBids[i]=nextBids[i];

    return allBids;
}

/**
 * @brief agent01::getMaxBudgetForAllKeywords
 * @return return maximum budget assigned for this day per keyword
 */
double* agent01::getMaxBudgetForAllKeywords(){
    double* allBudget = new double[numberOfKeywords];   //Garbage collection handled elsewhere
    for(int i=0;i<numberOfKeywords;i++)
        allBudget[i]=nextBudget[i];

    return allBudget;
}
